package erfgame.core.world.terrain;

import java.util.ArrayList;
import java.util.List;

import erfgame.core.Pair;
import erfgame.core.Point3D;
import erfgame.core.Rectangle3D;

public class TerrainUpdateGroup {
	
	public static final TerrainUpdateGroup split( TerrainUpdate terrainUpdate, int maxBlockWidth, int maxBlockHeight ) {
		// only splits on the x and y
		int blocksX = terrainUpdate.getWidth() / maxBlockWidth;
		if( terrainUpdate.getWidth() % maxBlockWidth != 0 ) {
			blocksX++;
		}
		int blocksY = terrainUpdate.getHeight() / maxBlockHeight;
		if( terrainUpdate.getHeight() % maxBlockHeight != 0 ) {
			blocksY++;
		}
		
		short[][][] terrainUpdateData = terrainUpdate.getTerrain();
		int[][][] terrainUpdateSummary = terrainUpdate.getSummary();
		
		int blockWidth = terrainUpdate.getWidth() / blocksX;
		int blockHeight = terrainUpdate.getHeight() / blocksY;
		int blockDepth = terrainUpdate.getDepth();
		
		int tx = 0;
		
		ArrayList<Pair<Point3D, TerrainUpdate>> groupUpdates = new ArrayList<Pair<Point3D, TerrainUpdate>>(blocksX * blocksY);
		
		for( int x=blocksX; x>0;  ) {
			x--;
			int width;
			if( x == 0 ) {
				width = terrainUpdate.getWidth() - tx;
			} else {
				width = blockWidth;
			}
			int ty = 0;
			for( int y=blocksY; y>0; ) {
				y--;
				int height;
				if( y == 0 ) {
					height = terrainUpdate.getHeight() - ty;
				} else {
					height = blockHeight;
				}
				short[][][] terrain = new short[width][height][];
				int[][][] summary = new int[width][height][];
				for( int i=width; i>0; ) {
					i--;
					System.arraycopy(terrainUpdateData[tx+i], ty, terrain[i], 0, height);
					System.arraycopy(terrainUpdateSummary[tx+i], ty, summary[i], 0, height);
				}
				
				TerrainUpdate subUpdate = new TerrainUpdate( terrain, width, height, blockDepth, summary);
				Point3D position = new Point3D(tx, ty, 0);
				Pair<Point3D, TerrainUpdate> pair = new Pair<Point3D, TerrainUpdate>(
						position,
						subUpdate
				);
				groupUpdates.add(pair);
				
				ty += blockHeight;
			}
			tx += blockWidth;
		}
		return new TerrainUpdateGroup( groupUpdates );
	}
	
	private static final List<Pair<Point3D, TerrainUpdate>> toUpdates( TerrainUpdate update ) {
		ArrayList<Pair<Point3D, TerrainUpdate>> updates = new ArrayList<Pair<Point3D, TerrainUpdate>>(1);
		updates.add( new Pair<Point3D, TerrainUpdate>( new Point3D( 0, 0, 0 ), update ) );
		return updates;
	}
	
	private List<Pair<Point3D, TerrainUpdate>> terrainUpdates;
	
	private Rectangle3D bounds;
	
	public TerrainUpdateGroup() {
		this( new ArrayList<Pair<Point3D, TerrainUpdate>>() );
	}
	
	public TerrainUpdateGroup( TerrainUpdate terrainUpdate ) {
		this( toUpdates( terrainUpdate ) );
	}
	
	public TerrainUpdateGroup( List<Pair<Point3D, TerrainUpdate>> terrainUpdates ) {
		this.terrainUpdates = terrainUpdates;
		calculateBounds();
	}
	
	public Rectangle3D getBounds() {
		return this.bounds;
	}
	
	public List<Pair<Point3D, TerrainUpdate>> getTerrainUpdates() {
		return this.terrainUpdates;
	}
	
	private void calculateBounds() {
		Point3D minBounds = null;
		Point3D maxBounds = null;
		for( int i=0; i<terrainUpdates.size(); i++ ) {
			Pair<Point3D, TerrainUpdate> pair = terrainUpdates.get( i );
			Point3D position = pair.getFirst();
			TerrainUpdate update = pair.getSecond();
			int minx = position.x;
			int miny = position.y;
			int minz = position.z;
			
			int maxx = minx + update.getWidth();
			int maxy = miny + update.getHeight();
			int maxz = minz + update.getDepth();
			
			if( minBounds == null ) {
				minBounds = new Point3D( minx, miny, minz );
			} else {
				minBounds.x = Math.min( minBounds.x, minx );
				minBounds.y = Math.min( minBounds.y, miny );
				minBounds.z = Math.min( minBounds.z, minz );
			}
			
			if( maxBounds == null ) {
				maxBounds = new Point3D( maxx, maxy, maxz );
			} else {
				maxBounds.x = Math.max( maxBounds.x, maxx );
				maxBounds.y = Math.max( maxBounds.y, maxy );
				maxBounds.z = Math.max( maxBounds.z, maxz );
			}
		}
		if( minBounds != null && maxBounds != null ) {
			this.bounds = new Rectangle3D( minBounds.x, minBounds.y, minBounds.z, maxBounds.x, maxBounds.y, maxBounds.z );
		} else {
			this.bounds = null;
		}
	}
	
}
